﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Tracking;
using Nsf.Samples.RetryActivitySample.Infrastructure.InternalInterfaces;
using Nsf.Samples.RetryActivitySample.Infrastructure.Utils;

namespace Nsf.Samples.RetryActivitySample.Service.Infrastructure
{
    public class WorkflowRuntimeMonitor
    {
        #region Instance

        protected static Object _instanceSyncObj = new Object();
        protected static WorkflowRuntimeMonitor _instance;

        public static WorkflowRuntimeMonitor Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_instanceSyncObj)
                    {
                        if (_instance == null)
                        {
                            _instance = new WorkflowRuntimeMonitor();
                        }
                    }
                }
                return _instance;
            }
        }

        #endregion

        #region Constructors

        protected WorkflowRuntimeMonitor()
        {

        }

        #endregion

        #region Internal Methods

        protected SqlTrackingQuery CreateSqlTrackingQuery()
        {
            var connectionString = ConfigurationManager.ConnectionStrings["TrackingConnectionString"];
            if (connectionString == null)
                throw new NotSupportedException("Слежение за изменением состояния рабочих потоков отключено");

            var sqlTrackingQuery = new SqlTrackingQuery(connectionString.ConnectionString);
            return sqlTrackingQuery;
        }

        protected SqlTrackingWorkflowInstance GetSqlTrackingWorkflowInstanceInternal(Guid workflowInstanceId)
        {
            var sqlTrackingQuery = CreateSqlTrackingQuery();
            SqlTrackingWorkflowInstance sqlTrackingWorkflowInstance;
            if (!sqlTrackingQuery.TryGetWorkflow(workflowInstanceId, out sqlTrackingWorkflowInstance))
                throw new InvalidOperationException(String.Format("Рабочий поток с идентификатором '{0}' не найден", workflowInstanceId));
            return sqlTrackingWorkflowInstance;
        }

        protected IList<SqlTrackingWorkflowInstance> GetSqlTrackingWorkflowInstancesInternal(Type workflowType)
        {
            var sqlTrackingQuery = CreateSqlTrackingQuery();
            var options = new SqlTrackingQueryOptions
                              {
                                  WorkflowStatus = WorkflowStatus.Running,
                                  WorkflowType = workflowType
                              };
            return sqlTrackingQuery.GetWorkflows(options);
        }


        protected List<RetryableActivityErrorInfo> GetRetryableActivitiesInternal(SqlTrackingWorkflowInstance sqlTrackingWorkflowInstance)
        {
            var closedQueues = (from userEvent in sqlTrackingWorkflowInstance.UserEvents
                                where userEvent.UserDataKey == "QueueClosed"
                                select (Guid) userEvent.UserData).ToHashSet();
            var retryableActivities = (from userEvent in sqlTrackingWorkflowInstance.UserEvents
                                       where (userEvent.UserDataKey == "QueueCreated") &&
                                             (!closedQueues.Contains(((RetryableActivityErrorInfo) userEvent.UserData).QueueName))
                                       orderby userEvent.EventOrder
                                       select (RetryableActivityErrorInfo) userEvent.UserData).ToList();
            return retryableActivities;
        }

        protected RetryableWorkflowErrorInfo GetRetryableWorkflowInternal(SqlTrackingWorkflowInstance sqlTrackingWorkflowInstance)
        {
            var retryableActivities = GetRetryableActivitiesInternal(sqlTrackingWorkflowInstance);
            var retryableWorkflow = new RetryableWorkflowErrorInfo()
            {
                WorkflowInstanceId = sqlTrackingWorkflowInstance.WorkflowInstanceId,
                WorkflowType = sqlTrackingWorkflowInstance.WorkflowType.FullName,
                RetryableActivities = retryableActivities
            };
            return retryableWorkflow;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Запрос рабочего потока, во время выполнения которого произошла ошибка, но есть возможность повторить попытку
        /// </summary>
        /// <param name="workflowInstanceId">Идентификатор рабочего потока</param>
        public RetryableWorkflowErrorInfo GetRetryableWorkflow(Guid workflowInstanceId)
        {
            var sqlTrackingWorkflowInstance = GetSqlTrackingWorkflowInstanceInternal(workflowInstanceId);
            return GetRetryableWorkflowInternal(sqlTrackingWorkflowInstance);
        }

        /// <summary>
        /// Запрос перечня рабочих потоков, во время выполнения которых произошла ошибка, но есть возможность повторной попытку
        /// </summary>
        /// <param name="workflowType">Тип рабочих потоков. null интерпретируется как все типы</param>
        public List<RetryableWorkflowErrorInfo> GetRetryableWorkflows(Type workflowType)
        {
            var sqlTrackingWorkflowInstances = GetSqlTrackingWorkflowInstancesInternal(workflowType);
            var retryableWorkflows = sqlTrackingWorkflowInstances.Select(obj => GetRetryableWorkflowInternal(obj)).ToList();
            return retryableWorkflows;
        }

        #endregion

    }
}
